package com.vimhe.easyimport.config;

import com.vimhe.easyimport.config.bean.CellConfig;
import com.vimhe.easyimport.config.bean.Configuration;
import com.vimhe.easyimport.config.bean.FileConfig;
import com.vimhe.easyimport.config.bean.RowConfig;
import com.vimhe.easyimport.config.bean.SheetConfig;
import com.vimhe.easyimport.exception.ImportException;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;

import javax.xml.XMLConstants;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

/**
 * 读取配置文件并转化为配置对象
 *
 * @author Vimhe
 */
public final class XmlConfigSource extends DefaultHandler implements ConfigSource {

    /**
     * 配置文件路径，默认为classes根目录下的“importConfig.xml”
     */
    private String configLocation = "importConfig.xml";

    private Configuration configuration;
    private Map<String, FileConfig> fileConfigs;
    private FileConfig fileConfig;
    private Map<Integer, SheetConfig> sheetConfigs;
    private SheetConfig sheetConfig;
    private RowConfig rowConfig;
    private Map<String, CellConfig> cellConfigs;
    private CellConfig cellConfig;

    /**
     * 解析标签中的值
     */
    private String currentValue;

    public XmlConfigSource() {
        init();
    }

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        switch (qName) {
            case "configuration":
                configuration = new Configuration();

                fileConfigs = new HashMap<>(16);
                break;
            case "file":
                fileConfig = new FileConfig();
                fileConfig.setFormat(attributes.getValue("format"));
                fileConfig.setConfiguration(configuration);

                String fName = attributes.getValue("name");
                fileConfig.setName(fName);

                sheetConfigs = new HashMap<>(128);
                break;
            case "sheet":
                sheetConfig = new SheetConfig();
                sheetConfig.setFileConfig(fileConfig);

                // 默认从第一个工作表开始读取
                String sIndex = attributes.getValue("index");
                sheetConfig.setIndex(sIndex == null ? 1 : Integer.valueOf(sIndex));

                String sTable = attributes.getValue("table");
                sheetConfig.setTable(sTable);
                break;
            case "row":
                rowConfig = new RowConfig();
                rowConfig.setSheetConfig(sheetConfig);

                // 默认从第一行开始读取
                String rStart = attributes.getValue("start");
                rowConfig.setStart(rStart == null ? 1 : Integer.valueOf(rStart));

                // xls最大65535行，xlsx最大1048576行
                // 该值代表最大读取到哪一行，大于start且小于实际数据行数时才有意义
                String rEnd = attributes.getValue("end");
                rowConfig.setEnd(rEnd == null ? 1048576 : Integer.valueOf(rEnd));

                String rCols = attributes.getValue("cols");
                rowConfig.setCols(rCols == null ? 1 : Integer.valueOf(rCols));

                cellConfigs = new HashMap<>(16);
                break;
            case "cell":
                cellConfig = new CellConfig();
                cellConfig.setRowConfig(rowConfig);

                // 默认从A列开始读取
                String cIndex = attributes.getValue("index");
                cellConfig.setIndex(cIndex == null ? "A" : cIndex);

                String cCol = attributes.getValue("column");
                cellConfig.setColumn(cCol);

                String cCode = attributes.getValue("code");
                cellConfig.setCode(cCode);

                // 默认数据类型是java.lang.String
                String cType = attributes.getValue("type");
                cellConfig.setType(cType == null ? "java.lang.String" : cType);

                // 默认可空
                String cNullable = attributes.getValue("nullable");
                Boolean cNullableB = "true".equalsIgnoreCase(cNullable) || "false".equals(cNullable)
                        ? Boolean.valueOf(cNullable) : null;
                cellConfig.setNullable(cNullableB);

                String cMin = attributes.getValue("min");
                cellConfig.setMin(cMin == null ? null : Integer.valueOf(cMin));

                String cMax = attributes.getValue("max");
                cellConfig.setMax(cMax == null ? null : Integer.valueOf(cMax));

                String cMinLength = attributes.getValue("minLength");
                cellConfig.setMinLength(cMinLength == null ? null : Integer.valueOf(cMinLength));

                String cLength = attributes.getValue("length");
                cellConfig.setLength(cLength == null ? null : Integer.valueOf(cLength));

                String cMaxLength = attributes.getValue("maxLength");
                cellConfig.setMaxLength(cMaxLength == null ? null : Integer.valueOf(cMaxLength));

                String cRegex = attributes.getValue("regex");
                cellConfig.setRegex(cRegex);
                break;
            default:
                break;
        }
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        switch (qName) {
            case "cell":
                cellConfigs.put(cellConfig.getIndex(), cellConfig);
                break;
            case "row":
                rowConfig.setCellConfigs(cellConfigs);
                break;
            case "sheet":
                sheetConfig.setRowConfig(rowConfig);
                sheetConfigs.put(sheetConfig.getIndex(), sheetConfig);
                break;
            case "file":
                fileConfig.setSheetConfigs(sheetConfigs);
                fileConfigs.put(fileConfig.getName(), fileConfig);
                break;
            case "configuration":
                configuration.setFileConfigs(fileConfigs);
                break;
            default:
                break;
        }
    }

    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        currentValue = new String(ch, start, length);
    }

    @Override
    public Configuration read() {
        return configuration;
    }

    /**
     * 解析配置文件<br>
     * 配置文件路径默认为classes根目录下的“importConfig.xml”
     */
    private void init() {
        InputStream xsdInputStream = null;
        InputStream xmlInputStream = null;
        try {
            // 开始配置文件有效性验证
            xsdInputStream = this.getClass().getClassLoader().getResourceAsStream("com/vimhe/easyimport/config/config.xsd");
            xmlInputStream = this.getClass().getClassLoader().getResourceAsStream(configLocation);
            SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
            Schema schema = factory.newSchema(new StreamSource(xsdInputStream));
            Validator validator = schema.newValidator();
            validator.validate(new StreamSource(xmlInputStream));

            // 开始解析
            xmlInputStream = this.getClass().getClassLoader().getResourceAsStream(configLocation);
            InputSource sheetSource = new InputSource(xmlInputStream);
            XMLReader xmlReader = XMLReaderFactory.createXMLReader();
            xmlReader.setContentHandler(this);
            xmlReader.parse(sheetSource);
        } catch (Exception e) {
            throw new ImportException("解析配置文件出错：" + e.getMessage());
        } finally {
            if (xsdInputStream != null) {
                try {
                    xsdInputStream.close();
                } catch (IOException ignored) {
                }
            }
            if (xmlInputStream != null) {
                try {
                    xmlInputStream.close();
                } catch (IOException ignored) {
                }
            }
        }
    }

    public void setConfigLocation(String configLocation) {
        this.configLocation = configLocation;
    }

}
